package ant.msvc;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.filters.ChainableReader;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.apache.tools.ant.taskdefs.condition.Os;
import org.apache.tools.ant.types.Environment;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.RedirectorElement;

public class MsvcTask extends Task
{
	private File msdev;
	private File project;
	private String config;
	private boolean rebuild = true;
	private boolean failOnError = true;
	private boolean quiet = false;
	private boolean showCommandLine = false;
	private boolean dontExecute = false;
	
	public void setMsdev(File msdev)
	{
		this.msdev = msdev;
	}
	
	public void setProject(File project)
	{
		this.project = project;
	}
	
	public void setConfig(String config)
	{
		this.config = config;
	}
	
	public void setRebuild(boolean rebuild)
	{
		this.rebuild = rebuild;
	}
	
	public void setFailOnError(boolean failOnError)
	{
		this.failOnError = failOnError;
	}
	
	public void setQuiet(boolean quiet)
	{
		this.quiet = quiet;
	}
	
	public void setShowCommandLine(boolean showCommandLine)
	{
		this.showCommandLine = showCommandLine;
	}
	
	public void setDontExecute(boolean dontExecute)
	{
		this.dontExecute = dontExecute;
	}
	
	@Override
	public void execute()
	{
		validate();
		
		log("Building " + project.getName() + " : " + config);
		
		List<String> cmdLine = new ArrayList<String>();
		
		if (msdev == null)
			cmdLine.add("msdev.exe");
		else
			cmdLine.add(getFullPath(msdev));
		cmdLine.add(getFullPath(project));
		cmdLine.add("/MAKE");
		cmdLine.add(config);
		if (rebuild)
			cmdLine.add("/REBUILD");
		
		if (showCommandLine)
			log(cmdLine);
		
		if (!dontExecute)
		{
			String output = execute(cmdLine);
			checkForError(output);
		}
	}
	
	private void checkForError(String output)
	{
		String lastLine = getLastLine(output);
		if (getNumErrors(lastLine) != 0)
		{
			if (failOnError)
				throw new BuildException(lastLine, getLocation());
			else
				log(lastLine, Project.MSG_ERR);
		}
	}
	
	private void log(List<String> cmdLine)
	{
		StringBuilder sb = new StringBuilder();
		sb.append("Executing");
		
		for (int i = 0; i < cmdLine.size(); i++)
		{
			sb.append(" ");
			sb.append(cmdLine.get(i));
		}
		
		log(sb.toString());
	}
	
	private String execute(List<String> cmdLine)
	{
		ExecTask exec = new ExecTask(this);
		String executable = cmdLine.get(0);
		exec.setExecutable(executable);
		for (int i = 1; i < cmdLine.size(); i++)
			exec.createArg().setValue(cmdLine.get(i));
		
		if (msdev == null)
		{
			exec.setResolveExecutable(true);
			exec.setSearchPath(true);
		}
		else
		{
			Environment.Variable path = new Environment.Variable();
			path.setKey("PATH");
			path.setPath(new Path(getProject(), getFullPath(msdev.getParentFile()) + ";" + System.getenv("PATH")));
			exec.addEnv(path);
		}
		exec.setDir(project.getParentFile());
		exec.setFailonerror(failOnError);
		exec.setLogError(true);
		
		StringBuilder output = new StringBuilder();
		exec.addConfiguredRedirector(createRedirector(output));
		
		exec.execute();
		
		return output.toString();
	}
	
	private RedirectorElement createRedirector(final StringBuilder output)
	{
		RedirectorElement redirector = new RedirectorElement();
		redirector.createOutputFilterChain().add(new ChainableReader() {
			public Reader chain(final Reader rdr)
			{
				return new Reader() {
					
					@Override
					public void close() throws IOException
					{
						rdr.close();
					}
					
					@Override
					public int read(char[] cbuf, int off, int len) throws IOException
					{
						int read = rdr.read(cbuf, off, len);
						output.append(cbuf, off, read);
						
						if (quiet)
							return 0;
						
						return read;
					}
				};
			}
		});
		return redirector;
	}
	
	String getLastLine(String output)
	{
		if (output == null)
			return "";
		
		int lastIndex = output.length();
		do
		{
			int index = output.lastIndexOf('\n', lastIndex - 1);
			if (index < 0)
				return output.substring(0, lastIndex);
			
			String line = output.substring(index, lastIndex).trim();
			lastIndex = index;
			if (!line.isEmpty())
				return line;
			
		} while (true);
	}
	
	private Pattern summaryRE = Pattern.compile("[^-]+ - ([0-9]+) error\\(s\\), ([0-9]+) warning\\(s\\)");
	
	int getNumErrors(String lastLine)
	{
		Matcher m = summaryRE.matcher(lastLine);
		if (!m.matches())
			return -1;
		
		try
		{
			return Integer.parseInt(m.group(1));
		}
		catch (NumberFormatException e)
		{
			return -1;
		}
	}
	
	private void validate()
	{
		if (!Os.isOs("windows", null, null, null))
			throw new BuildException("msvc task only works in windows");
		if (project == null)
			throw new BuildException("No project file expecified");
		if (!project.exists())
			throw new BuildException("File doesn't exist: " + getFullPath(project));
		if (msdev != null && !msdev.exists())
			throw new BuildException("msdev file doesn't exist: " + getFullPath(msdev));
		
		if (config == null || config.trim().isEmpty())
			throw new BuildException("No config expecified");
	}
	
	private String getFullPath(File dspFile)
	{
		try
		{
			return dspFile.getCanonicalPath();
		}
		catch (IOException e)
		{
			throw new BuildException(e);
		}
	}
	
}
